#!/bin/bash
# shell 常用库函数集合
# 1, 版本号正则表达式，用于grep -oP 
V_VERSION_REGEX='(?<=^|\s)v[0-9]+(\.[0-9]+)+(?=$|\s)'
VERSION_REGEX='(?<=^|\s)[0-9]+(\.[0-9]+)+(?=$|\s)'
# 1.1 从字符串获取版本号，格式为"vN.M"
get_v_version() {
  echo "$1" | grep -oP $V_VERSION_REGEX
}
# 1.2 从字符串获取版本号，格式为"N.M"
get_version() {
  echo "$1" | grep -oP $VERSION_REGEX
}

# check if the current version is greater than or equal to the target version.
# 3, 检查当前版本是否大于或等于目标版本。
check_version_ge() {
    test "$(echo "$@" | tr " " "\n" | sort -rV | head -n 1)" == "$1"
}


# 2, #打印有颜色的字体
function pblue() {
 echo -e "\033[34m $1 \033[0m"
}

function pyellow() {
 echo -e "\033[33m $1 \033[0m"
}

function pred() {
 echo -e "\033[31m $1 \033[0m"
}

function pgreen() {
 echo -e "\033[32m $1 \033[0m"
}


# 3, 

PACKAGE_MANAGER=$((command -v apt-get > /dev/null && echo apt-get) \
    || (command -v yum > /dev/null && echo yum) \
    || (echo "Neither apt-get nor yum is installed, please install one of them first." && exit 1))    

FORCE=${FORCE:-false}
# 检查是否有curl和wget
HAS_CURL="$(command -v curl >/dev/null 2>&1 && echo true || echo false)"
HAS_WGET="$(command -v wget >/dev/null 2>&1 && echo true || echo false)"
# 检查本机的软件管理工具是用apt-get还是yum
HAS_APT="$(command -v apt-get >/dev/null 2>&1 && echo true || echo false)"
HAS_YUM="$(command -v yum >/dev/null 2>&1 && echo true || echo false)"

bash_xc='bash -xc'

if ! $HAS_CURL && ! $HAS_WGET; then
  echo "Either curl or wget is required"
  exit 1
fi

#usage: download_url <url> [target] 
download_url() {
  local target
  local url=$1
  local remote_file=""
  
  if [ $# -eq 2 ]; then
    target=$2
  elif [ $# -eq 1 ]; then 
    target=${1##*/}
  else
    echo "cmd err, usage: download_url <url> [target]"
    return 1
  fi
  if [ ! -f "$target" ] || ${FORCE} ; then
    if $HAS_CURL; then
      $bash_xc "curl -fsSL $url -o $target"
      if [ $? -ne 0 ]; then
	      echo "curl or wget failed"
        return 3
      fi
    elif $HAS_WGET; then
      $bash_xc "wget -q $url -O $target"
      if [ $? -ne 0 ]; then
	      echo "curl or wget failed"
        return 3
      fi
    fi
    echo "get $(basename $target) successfully"
    return 0
  else
    echo "do not get $(basename $url): file exist or force=${FORCE} "
    return 0
  fi
  return 0
}

download_url_until_succeed() {
  local target
  local url=$1
  
  if [ $# -eq 2 ]; then
    target=$2
  elif [ $# -eq 1 ]; then 
    target=${1##*/}
  else
    echo "cmd err, usage: download_url <url> [target]"
    return 1
  fi
  echo "download $url $target"
  
  while ! download_url $url $target; do
    sleep 5
  done
}

clean_locks() {
    if $HAS_APT; then
        rm -rf /var/lib/dpkg/lock
        rm -rf /var/lib/dpkg/lock-frontend
        rm -rf /var/cache/apt/archives/lock
    elif $HAS_YUM; then
        rm -rf /var/run/yum.pid
        rm -rf /var/run/yum.pid.old
        # rm -rf /var/cache/yum/*
    fi
}



# get the latest version of packages.
# usage: get_latest_version <pkg_name>
# 获取组件的最新版本。
# 使用方法： get_latest_version <软件包名称>
get_latest_version() {
    local pkg_name=$1
    if $HAS_APT; then
        echo $(apt-cache madison $pkg_name | head -1 | grep -Eo "$VERSION_REGEX")
    elif $HAS_YUM; then
        echo $(yum list $pkg_name --showduplicates | grep -Eo "$VERSION_REGEX" | sort -rV | head -1)
    fi
}

# delete packages.
# usage: delete_pkg <pkg_name>
# 删除软件包。
# 使用方法： delete_pkg <软件包名称>
delete_pkg() {
    local pkg_name=$1
    if $HAS_APT; then
        local pkgs=$(dpkg -l | grep -E "$pkg_name" | awk '{print $2}')
        if [ -n "$pkgs" ] ; then
            set -x
            apt-get remove -y $pkgs > /dev/null 2>&1
            apt-get autoremove -y > /dev/null
            apt-get autoclean -y > /dev/null
            set +x
        fi
    elif $HAS_YUM; then
        local pkgs=$(rpm -qa | grep -E 'helm' | awk '{print $1}')
        if [ -n "$pkgs" ] ; then
            set -x
            yum remove -y $pkgs > /dev/null 2>&1
            yum autoremove -y > /dev/null
            yum clean all > /dev/null
            set +x
        fi
    fi
}


parse_yaml() {
    local yaml_file=$1
    local prefix=$2
    local s
    local w
    local fs

    s='[[:space:]]*'
    w='[a-zA-Z0-9_.-]*'
    fs="$(echo @ | tr @ '\034')"

    (
        sed -e '/- [^\"]'"[^\']"'.*: /s|\([ ]*\)- \([[:space:]]*\)|\1-\'$'\n''  \1\2|g' |
            sed -ne '/^--/s|--||g; s|\"|\\\"|g; s/[[:space:]]*$//g;' \
                -e 's/\$/\\\$/g' \
                -e "/#.*[\"\']/!s| #.*||g; /^#/s|#.*||g;" \
                -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
                -e "s|^\($s\)\($w\)${s}[:-]$s\(.*\)$s\$|\1$fs\2$fs\3|p" |
            awk -F"$fs" '{
            indent = length($1)/2;
            if (length($2) == 0) { conj[indent]="+";} else {conj[indent]="";}
            vname[indent] = $2;
            for (i in vname) {if (i > indent) {delete vname[i]}}
                if (length($3) > 0) {
                    vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
                    printf("%s%s%s%s=(\"%s\")\n", "'"$prefix"'",vn, $2, conj[indent-1], $3);
                }
            }' |
            sed -e 's/_=/+=/g' |
            awk 'BEGIN {
                FS="=";
                OFS="="
            }
            /(-|\.).*=/ {
                gsub("-|\\.", "_", $1)
            }
            { print }'
    ) <"$yaml_file"
}

#yaml文件解析
unset_variables() {
    # Pulls out the variable names and unsets them.
    #shellcheck disable=SC2048,SC2206 #Permit variables without quotes
    local variable_string=($*)
    unset variables
    variables=()
    for variable in "${variable_string[@]}"; do
        tmpvar=$(echo "$variable" | grep '=' | sed 's/=.*//' | sed 's/+.*//')
        variables+=("$tmpvar")
    done
    for variable in "${variables[@]}"; do
        if [ -n "$variable" ]; then
            unset "$variable"
        fi
    done
}

create_variables() {
    local yaml_file="$1"
    local prefix="$2"
    local yaml_string
    yaml_string="$(parse_yaml "$yaml_file" "$prefix")"
    unset_variables "${yaml_string}"
    eval "${yaml_string}"
}


variable_expansion() {
    local srcYaml=$1
    local targetYaml=$2
    local vars=$3
    [ -n "$vars" ] && eval "$vars"
    eval $(yq -o=shell $srcYaml | xargs -I {} echo {})
    eval "cat <<EOF
$(< $srcYaml)
EOF" > $targetYaml
}

#仅打印变量a=b
#结果可以用eval $(print_variables $file)
#或者export $(print_variables $file)
print_yaml_var() {
    local yaml_file="$1"
    local prefix="$2"
    local yaml_string
    yaml_string="$(parse_yaml "$yaml_file" "$prefix")"
    unset_variables "${yaml_string}"
    #结果显示为a="x"
    #echo "${yaml_string}" | sed 's/=(/=/1' |sed 's/)$//1'  | sed '/=\"\\\".*\\\"\"$/ s|="\\"|="|1; s|\\""$|"|1'

    #结果显示为a=x
    #echo "${yaml_string}" | sed 's/=("/=/1' |sed 's/")$//1' | sed '/=\\\".*\\\"$/ s|=\\"|=|1; s|\\"$||1' 
    echo "${yaml_string}" | sed 's/=("/=/1' |sed 's/")$//1' | sed 's|\\"|"|g' | sed '/=".*"$/s|"$||1;s|="|=|1'
}

#仅打印变量a=b
#结果可以用eval $(print_variables $file)
#或者export $(print_variables $file)
yml_to_var() {
  yq e -o props $1 | grep -vE "^[ \t]*#|^$" | awk -F ' = ' '{gsub(/\./, "_", $1); print $1"="$2}'
}
export_yaml_var() {
  if ! command -v yq >/dev/null 2>&1; then
     export $(yml_to_var $1)
  else
     export $(print_yaml_var $1)
  fi
}

#编辑yaml文件，只支持指定文件的第二层数据修改
#例如
#PKGS:
#  kubeadm: 1.21.11
#  docker:
#     version: 20.10.21 #不支持编辑到此层
edit_yaml_value_Lv2() {
  local key=$1
  local value=$2
  local file=$3
  sed -i "s|^  $key:.*|  $key: $value|" $file
}


#获得当前主机的cgroup driver
get_cgroupdriver() {
  local plat=$(uname -m)
  case $plat in
    x86_64|aarch64) echo "systemd" ;;
    mips64el|mips64le|mips64) echo "cgroupfs" ;;
    *) echo "unknown plat" ;;
  esac
}

##=============网络相关==================
#获得默认网关对应的接口名称
get_default_dev() {
  #echo $(ip route list | grep default | grep -oE "dev [^ ]{1,}"| awk -F ' ' '{print $2}')
  echo $(ip route list table all | grep "^default" | awk '{print $5}' | uniq | head -n1)
}
#获得接口上的IP地址和掩码，格式为a.b.c.d/m
get_dev_ipMask() {
  echo $(ip -f inet -o addr show dev $1 | grep brd | awk '{print $4}')
}
get_dev_ip() {
  echo $(ip -f inet -o addr show dev $1 | grep brd | awk '{sub(/\/.*$/, "", $4); print $4}')
}


#获得主机上默认联网的IP
get_default_ip() {
  local dev=$(get_default_dev)
  echo $(ip -f inet -o addr show dev $dev | head -n 1 | awk -F ' ' '{print $4}' | cut -d '/' -f 1)
}

#判断IP是否是主机上的IP
check_ip_is_local() {
  return $([[ "$1" == "$(ip addr show | awk '/inet / {print $2}' | cut -d '/' -f 1 | grep -o $1)" ]])
}

#判断IP是否是一个合法的IP地址
ipIsValid() {
  local ipaddr=$1
  local ip_regexp='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
  return $([[ $ipaddr =~ $ip_regexp ]])
}

#判断a.b.c.d/m是否合法
ipmaskIsValid() {
  local ipmask=$1
  local ipmask_regexp='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/([12]?[0-9]|3[012])$'

  return $([[ $ipmask =~ $ipmask_regexp ]])
}

#将正整数转换为子网掩码
maskNumToStr() {
  local num=$1
  if ! [[ "$num" =~ ^[1-9][0-9]*$ ]]; then
    echo "Invalid input: $num is not a positive integer" >&2
    return 1
  fi
  if [ $num -gt 32 ]; then
    echo "Invalid input: $num is greater than 32" >&2
    return 1
  fi
  local mask=$((0xffffffff << (32 - $num)))
  echo "$((($mask >> 24) & 0xff)).$((($mask >> 16) & 0xff)).$((($mask >> 8) & 0xff)).$(($mask & 0xff))"
}

#判断a.b.c.d/m是否包含某个IP，
#使用说明： 判断a.b.c.d/m是否包含地址1.2.3.4: 
# if subnet_contains_ip 1.2.3.0/24 1.2.3.4 ; then
#      echo "1.2.3.0/24 contains 1.2.3.4"
# else
#      echo "1.2.3.0/24 not contains 1.2.3.4"
# fi
subnet_contains_ip() {
  subnet=$1
  ip=$2

  # 提取子网地址和掩码长度
  subnet_addr=$(echo $subnet | cut -d '/' -f 1)
  subnet_mask_len=$(echo $subnet | cut -d '/' -f 2)

  # 将子网地址和IP地址转换为32位二进制数字
  IFS='.' read -r -a subnet_addr_arr <<< "$subnet_addr"
  subnet_addr_bin=$(printf '%08d' $(echo "obase=2;${subnet_addr_arr[0]}" | bc))
  for i in {1..3}; do
    subnet_addr_bin="$subnet_addr_bin$(printf '%08d' $(echo "obase=2;${subnet_addr_arr[$i]}" | bc))"
  done

  IFS='.' read -r -a ip_arr <<< "$ip"
  ip_bin=$(printf '%08d' $(echo "obase=2;${ip_arr[0]}" | bc))
  for i in {1..3}; do
    ip_bin="$ip_bin$(printf '%08d' $(echo "obase=2;${ip_arr[$i]}" | bc))"
  done

  # 按照掩码长度截取子网地址和IP地址的二进制数字
  subnet_addr_bin="${subnet_addr_bin:0:$subnet_mask_len}"
  ip_bin="${ip_bin:0:$subnet_mask_len}"

  # 判断IP是否在子网中
  if [ "$subnet_addr_bin" = "$ip_bin" ]; then
    return 0
  else
    return 1
  fi
}

#添加etc hosts， 
#使用方法：set_etchosts 1.2.3.4 www.a.com
set_etchosts() {
  local ip=$1
  local host=$2
  sed -i "/$host/d" /etc/hosts
  echo "$ip $host" >> /etc/hosts
}

#json或者yaml文件，给数组添加成员
#使用说明: yq_add_array <filename> <array-name> <a1,a2,...>
#举例：
#root@k8s-master:~/szsciit/cnos/ske# cat /etc/docker/daemon.json
# {
#   "insecure-registries": [
#     "https://registry.vivio-sz.com",
#     "registry.vivio-sz.com",
#     "edge.local.io:5000"
#   ]
# }
#使用说明: yq_add_array /etc/docker/daemon.json .insecure-registries a.com,b.com,x.com
#  "insecure-registries": [
#    "https://registry.vivio-sz.com",
#    "registry.vivio-sz.com",
#    "edge.local.io:5000",
#    "a.com",
#    "b.com",
#    "x.com"
#  ]
yq_add_array() {
  local file=$1
  local yqPath=$2
  local data=$3
  local new=$(echo "[\"$data\"]" | sed 's/,/\",\"/g')
  yq -i "$yqPath += $new | $yqPath |= unique"  -o json $file
}
#yq_add_array /etc/docker/daemon.json .insecure-registries a.com,b.com,x.com
